const jwt = require('jsonwebtoken');
const moment = require('moment');
const httpStatus = require('http-status');
const config = require('../config/config');
const { Token, User } = require('../models');
const { tokenTypes } = require('../config/tokens');
const ApiError = require('../utils/ApiError');
const redis = require('../config/redis');

/**
 * 通过payload直接生成Token
 * @param {Object} payload - Token载荷
 * @param {string} expiresIn - 过期时间(例如：'24h', '60m')
 * @param {string} [secret=config.jwt.secret] - 密钥
 * @returns {string} - JWT Token
 */
const generateToken = (payload, expiresIn, secret = config.jwt.secret) => {
  return jwt.sign(payload, secret, { expiresIn });
};

/**
 * 生成带标准字段的Token
 * @param {string} userId - 用户ID
 * @param {Date} expires - 过期时间
 * @param {string} type - Token类型
 * @param {string} [secret=config.jwt.secret] - 密钥
 * @returns {string} - JWT Token
 */
const generateTokenWithUserId = (userId, expires, type, secret = config.jwt.secret) => {
  const payload = {
    sub: userId,
    iat: moment().unix(),
    exp: expires.unix(),
    type,
  };
  return jwt.sign(payload, secret);
};

/**
 * 保存Token
 * @param {string} token - Token字符串
 * @param {string} userId - 用户ID
 * @param {Date} expires - 过期时间
 * @param {string} type - Token类型
 * @param {boolean} [blacklisted=false] - 是否已注销
 * @returns {Promise<Token>} - 保存的Token
 */
const saveToken = async (token, userId, expires, type, blacklisted = false) => {
  const tokenDoc = await Token.create({
    token,
    user: userId,
    expires: expires.toDate(),
    type,
    blacklisted,
  });
  return tokenDoc;
};

/**
 * 验证Token是否合法并未过期
 * @param {string} token - Token字符串
 * @param {string} type - Token类型
 * @returns {Promise<Token>} - Token对象
 */
const verifyToken = async (token, type) => {
  try {
    const payload = jwt.verify(token, config.jwt.secret);
    const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
    if (!tokenDoc) {
      throw new Error('Token不存在');
    }
    return tokenDoc;
  } catch (error) {
    throw new ApiError(httpStatus.UNAUTHORIZED, 'Token无效');
  }
};

/**
 * 生成Auth Tokens
 * @param {User} user - 用户对象
 * @param {string} [userType='user'] - 用户类型，'admin'或'user'
 * @returns {Object} - access和refresh tokens
 */
const generateAuthTokens = async (user, userType = 'user') => {
  const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
  
  // 构建token载荷，添加用户类型
  const payload = {
    sub: user.id,
    iat: moment().unix(),
    exp: accessTokenExpires.unix(),
    type: tokenTypes.ACCESS,
    userType: userType // 添加用户类型字段
  };
  
  // 如果是管理员，则添加角色和权限
  if (userType === 'admin') {
    payload.role = user.role;
    payload.permissions = user.permissions;
  }
  
  const accessToken = jwt.sign(payload, config.jwt.secret);

  const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
  
  // 刷新token也包含用户类型
  const refreshPayload = {
    sub: user.id,
    iat: moment().unix(),
    exp: refreshTokenExpires.unix(),
    type: tokenTypes.REFRESH,
    userType: userType
  };
  
  const refreshToken = jwt.sign(refreshPayload, config.jwt.secret);
  await saveToken(refreshToken, user.id, refreshTokenExpires, tokenTypes.REFRESH);

  // 缓存访问令牌以加快验证
  try {
    // 从配置中获取Redis令牌过期时间
    const expiryInSeconds = config.jwt.redisTokenExpiryDays * 24 * 60 * 60; // 转换为秒
    await redis.set(`auth:${userType}:${user.id}`, accessToken, expiryInSeconds);
    console.log(`已将令牌缓存到Redis，过期时间为${config.jwt.redisTokenExpiryDays}天（${expiryInSeconds}秒）`);
  } catch (error) {
    // 如果Redis不可用，继续执行不中断
    console.error('Redis缓存令牌失败:', error);
  }

  return {
    access: {
      token: accessToken,
      expires: accessTokenExpires.toDate(),
    },
    refresh: {
      token: refreshToken,
      expires: refreshTokenExpires.toDate(),
    },
  };
};

/**
 * 生成重置密码Token
 * @param {string} email - 邮箱
 * @returns {Promise<string>} - Token
 */
const generateResetPasswordToken = async (email) => {
  const user = await User.findOne({ email });
  if (!user) {
    throw new ApiError(httpStatus.NOT_FOUND, '找不到此电子邮件对应的用户');
  }
  const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
  const resetPasswordToken = generateTokenWithUserId(user.id, expires, tokenTypes.RESET_PASSWORD);
  await saveToken(resetPasswordToken, user.id, expires, tokenTypes.RESET_PASSWORD);
  return resetPasswordToken;
};

/**
 * 生成验证邮箱Token
 * @param {User} user - 用户对象
 * @returns {Promise<string>} - Token
 */
const generateVerifyEmailToken = async (user) => {
  const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
  const verifyEmailToken = generateTokenWithUserId(user.id, expires, tokenTypes.VERIFY_EMAIL);
  await saveToken(verifyEmailToken, user.id, expires, tokenTypes.VERIFY_EMAIL);
  return verifyEmailToken;
};

module.exports = {
  generateToken,
  generateTokenWithUserId,
  saveToken,
  verifyToken,
  generateAuthTokens,
  generateResetPasswordToken,
  generateVerifyEmailToken,
}; 